using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using NHibernate;
using NHibernate.Criterion;

using Pampa.CRUD.Core;
using Pampa.CRUD.Exceptions;

using Pampa.Grid.Core;
using Pampa.Grid.Core.Filters;
using Pampa.Grid.Data;
using Pampa.Grid.Exceptions;
using Pampa.MVC.Models;

namespace Pampa.MVC.Data
{
    public class GridDataReaderCastle : GridDataReaderEntityBase
    {
        public GridDataReaderCastle() : base()
        {
        }

        public GridDataReaderCastle(string entityName) : base(entityName)
        {
        }

        protected override int CountEntities(Configuration configuration)
        {
            var criteria = GetCriteria(configuration, 0, 0, false);
            criteria.SetProjection(Projections.Count("Id"));
            return (int)criteria.UniqueResult();
        }

        protected override void GetEntities(Configuration configuration, int pageSize, int pageNumber)
        {
            var criteria  = GetCriteria(configuration, pageSize, pageNumber, true);
            entities = criteria.List<object>().ToArray();
        }

        private ICriteria GetCriteria(Configuration configuration, int pageSize, int pageNumber, bool withOrder)
        {
            Type type = Type.GetType(this.EntityName);

            var session = ActiveRecordMediator.GetSessionFactoryHolder()
                                              .CreateSession(type);

            var criteria = session.CreateCriteria(type);

            AddCriterion(criteria, configuration.StaticFilter);
            AddCriterion(criteria, configuration.MainFilter);
            AddCriterion(criteria, configuration.QuickFilter);
            
            if (withOrder)
            {
                AddOrders(criteria, configuration.OrderBy);
            }

            if (pageSize != 0)
            {
                criteria.SetFirstResult(pageSize * (pageNumber - 1));
                criteria.SetMaxResults(pageSize);
            }

            return criteria;
        }

        private void AddCriterion(ICriteria criteria, GroupFilter groupFilter)
        {
            IList<string> fields = new List<string>();
            ICriterion criterion = GetCriterion(groupFilter, fields);

            if(criterion != null) 
            {
                foreach (var field in fields)
                {
                    AddAliases(criteria, field);
                }

                criteria.Add(criterion);
            }
        }

        private void AddOrders(ICriteria criteria, IEnumerable<KeyValuePair<string, OrderDirection>> orderBy)
        {
            foreach (var configOrder in orderBy)
            {
                var aliasProp = AddFieldAlias(criteria, configOrder.Key);
                var order = new Order(aliasProp, (configOrder.Value == OrderDirection.Ascending));
                criteria.AddOrder(order);
            }
        }

        private ICriterion GetCriterion(GroupFilter groupFilter, IList<string> fields)
        {
            ICriterion criterion = null;

            foreach (Filter filter in groupFilter.InnerFilters)
            {
                ICriterion innerCriterion = null;

                if (filter is SimpleFilter)
                    innerCriterion = GetCriterionFromSimpleFilter((SimpleFilter)filter, fields);
                else if (filter is GroupFilter)
                    innerCriterion = GetCriterion((GroupFilter)filter, fields);
                else
                    throw (new DataReaderException("Filter not supported [" + filter.GetType().Name + "]"));

                if (innerCriterion != null)
                {
                    if (criterion == null)
                        criterion = innerCriterion;
                    else if (groupFilter is AndGroupFilter)
                        criterion = Restrictions.And(criterion, innerCriterion);
                    else if (groupFilter is OrGroupFilter)
                        criterion = Restrictions.Or(criterion, innerCriterion);
                }
            }

            return criterion;
        }

        private ICriterion GetCriterionFromSimpleFilter(SimpleFilter simpleFilter, IList<string> fields)
        {
            ICriterion criteria = null;

            var fieldAlias = GetFieldAlias(simpleFilter.Field);

            switch (simpleFilter.Operation)
            {
                case OperationType.IsNull:
                    criteria = Restrictions.IsNull(fieldAlias);
                    break;
                case OperationType.IsNotNull:
                    criteria = Restrictions.IsNotNull(fieldAlias);
                    break;
                case OperationType.Equal:
                    criteria = Restrictions.Eq(fieldAlias, simpleFilter.Value);
                    break;
                case OperationType.Grater:
                    criteria = Restrictions.Gt(fieldAlias, simpleFilter.Value);
                    break;
                case OperationType.GraterEqual:
                    criteria = Restrictions.Ge(fieldAlias, simpleFilter.Value);
                    break;
                case OperationType.Less:
                    criteria = Restrictions.Lt(fieldAlias, simpleFilter.Value);
                    break;
                case OperationType.LessEqual:
                    criteria = Restrictions.Le(fieldAlias, simpleFilter.Value);
                    break;
                case OperationType.NotEqual:
                    criteria = Restrictions.Not(Restrictions.Eq(simpleFilter.Field, simpleFilter.Value));
                    break;
                case OperationType.StartsWith:
                    criteria = Restrictions.Like(fieldAlias, simpleFilter.Value.ToString(), MatchMode.Start);
                    break;
                case OperationType.Contains:
                    criteria = Restrictions.Like(fieldAlias, simpleFilter.Value.ToString(), MatchMode.Anywhere);
                    break;
                case OperationType.EndsWith:
                    criteria = Restrictions.Like(fieldAlias, simpleFilter.Value.ToString(), MatchMode.End);
                    break;
            }

            // adding fields to be consider for aliasing
            fields.Add(simpleFilter.Field);

            return criteria;
        }

        private void AddAliases(ICriteria criteria, string field)
        {
            var fieldtoadd = field;
            while (fieldtoadd != null)
            {
                AddFieldAlias(criteria, fieldtoadd);
                fieldtoadd = RemoveLastPart(fieldtoadd);
            }
        }

        private string AddFieldAlias(ICriteria criteria, string field)
        {
            string foreingProp;
            string foreignAlias;
            string fieldAlias = GetFieldAlias(field, out foreingProp, out foreignAlias);

            if (foreingProp != null)
            {
                if (criteria.GetCriteriaByPath(foreingProp) == null)
                {
                    criteria.CreateAlias(foreingProp, foreignAlias);
                }
            }

            return fieldAlias;
        }

        private string GetFieldAlias(string field)
        {
            string foreingProp;
            string foreignAlias;
            return GetFieldAlias(field, out foreingProp, out foreignAlias);
        }

        private string GetFieldAlias(string field, out string foreingProp, out string foreignAlias)
        {
            var fieldParts = field.Split('.');

            if (fieldParts.Length > 1)
            {
                foreingProp = field.Replace("." + fieldParts.Last(), "");
                foreignAlias = foreingProp.Replace(".", "_");

                return foreignAlias + "." + fieldParts.Last();
            }
            else
            {
                foreingProp = null;
                foreignAlias = null;

                return field;
            }
        }

        private string RemoveLastPart(string field)
        {
            var fieldParts = field.Split('.');
            if (fieldParts.Length > 1)
            {
                return field.Replace("." + fieldParts.Last(), "");
            }
            else
            {
                return null;
            }
        }
    }
}
